#include <stdio.h>
#include <deque>
#include <boost/thread.hpp>
#include "Constants.h"

namespace std {
using boost::mutex;
using boost::condition_variable;
using boost::unique_lock;
using boost::thread;
}

/**
 * Template class for a FIFO data structure. \n
 * T must be have a copy constructor and comparable. \n
 * Queue's operations are synchronized
 */
template<typename T>
class ThreadSafeQueue {
public:

	/**
	 * Pops and returns queue's head element
	 * @return queue's head element
	 */
	T pop() {

		std::unique_lock<std::mutex>
		mlock(mutex_);
		while (queue_.empty()) {
			cond_.wait(mlock);
		}
		T val = queue_.front();

		queue_.pop_front();

		if (m_currSize > 0) {
			--m_currSize;
		}

		// check if needed:
		mlock.unlock();
		return val;
	}

	/**
	 * Pops queue's head element and sets it to given output parameter
	 * @param[out] item holds popped head element
	 */
	void pop(T& item) {

		std::unique_lock<std::mutex> mlock(mutex_);
		while (queue_.empty()) {
			cond_.wait(mlock);
		}

		item = queue_.front();

		if (m_currSize > 0) {
			--m_currSize;
		}

		queue_.pop_front();

		// check if needed:
		mlock.unlock();
	}

	/**
	 * Adds given element to queue's end
	 * @param element to add
	 */
	void push(const T& item) {

		std::unique_lock<std::mutex> mlock(mutex_);

		if (m_currSize > m_maxSize) {
			return;
		}

		++m_currSize;
		queue_.push_back(item);

		mlock.unlock();
		cond_.notify_one();
	}

	/**
	 * Returns queue's head element without removing it
	 * @return queue's head (not removed)
	 */
	T front() {
		std::unique_lock<std::mutex> mlock(mutex_);
		T top = queue_.front();
		mlock.unlock();
		cond_.notify_one();
		return top;
	}

	/**
	 * Checks if the queue is empty
	 * @return True if the queue is empty, False if not
	 */
	bool empty() {
		std::unique_lock<std::mutex> mlock(mutex_);
		bool isEmpty = queue_.empty();
		mlock.unlock();
		cond_.notify_one();
		return isEmpty;
	}

	/**
	 * Constructor- creates a new queue
	 */
	ThreadSafeQueue() {
		m_maxSize = 0;
		m_currSize = 0;

	}

	/**
	 * Returns how many elements are in the queue
	 * @return queue's size
	 */
	int getSize() const {
		return m_currSize;
	}

	/**
	 * Sets maximal capacity for the queue
	 * @param size queue's maximal size
	 */
	void setMaxSize(const int size) {
		m_maxSize = size;
	}

	/**
	 * Returns index of given element
	 * @param element element to find its index
	 * @return index of given element, or ERROR_INDEX if not contained
	 */
	int indexOf(const T element) {

		std::unique_lock<std::mutex> mlock(mutex_);

		for (int i = 0; i < m_currSize; ++i) {
			if (element == queue_[i]) {
				return i;
			}
		}

		mlock.unlock();

		return ERROR_INDEX;
	}

	/**
	 * Returns index'th element (count starts from head)
	 * @param index of wanted element
	 * @return index'th element
	 */
	T getElement(int index) {
		std::unique_lock<std::mutex> mlock(mutex_);
		T temp_element = queue_[index];
		mlock.unlock();

		return temp_element;
	}

    /**
     * Removes index'th element (count starts from head) from the queue
     * @param index of element to be removed
     * @return True if element is removed, False if not
     * (element is removed if given index does not exceed queue's size)
     */
	bool erase(int index) {

		std::unique_lock<std::mutex> mlock(mutex_);
		// check if has an element at given position
		if (index >= m_currSize) {
			return false;
		}

		// remove element
		queue_.erase(queue_.begin() + index);
		--m_currSize;

		mlock.unlock();
		return true;
	}

private:

	std::deque<T> queue_;
	std::mutex mutex_;
	std::condition_variable cond_;
	int m_maxSize;
	int m_currSize;

	ThreadSafeQueue& operator=(const ThreadSafeQueue&); // disable assignment
	ThreadSafeQueue(const ThreadSafeQueue& queue) :
			m_maxSize(queue.m_maxSize), m_currSize(queue.m_currSize) {
	} // disable copying
};
